<?php
/**
 * @package panel
 * @subpackage panel.environment.db
 */
/**
 * CP db layer
 * @author Dmitry Bakaleinik (code)
 */
class panel__db
	implements maf__i__singletone {

	/**
	 * @var maf__helpers__pdo_connection
	 */
	protected $connection;

	protected $executed = 0;

	/**
	 * @return panel__db
	 */
	static public function getInstance() {

		static $instance;

		if( !isset($instance) ) {
			$instance = new self; }

		/**
		 * @var panel__db
		 */
		return $instance;

	}	# Function

	protected function __clone() {
	}	# Function

	protected function __sleep() {
	}	# Function

	protected function __wakeup() {
	}	# Function

	/**
	 * Connect at construct
	 */
	protected function __construct() {

		$this->connect();

	}	# Function

	/**
	 * Test db connection
	 * @return bool
	 */
	public function testConnection() {

		if( !count( p()->db()->select()->setSelect("1", "test", TRUE)->execute() ) ) {
			throw new panel__e__exception("Database test connection is failed."); }

		return TRUE;

	}	# Function

	/**
	 * @return int
	 */
	public function getSQLExecutionCount() {

		return $this->executed;

	}	# Function

	/**
	 * Connect to mysql
	 * @return maf__helpers__pdo_connection
	 */
	public function connect() {

		return $this->connection = new maf__helpers__pdo_connection(
			panel__settings__db::getHost(),
			panel__settings__db::getUsername(),
			panel__settings__db::getPassword(),
			array(
				PDO::ATTR_ERRMODE          => PDO::ERRMODE_EXCEPTION,
				PDO::ATTR_ORACLE_NULLS     => PDO::NULL_TO_STRING,
				PDO::ATTR_EMULATE_PREPARES => TRUE,
				PDO::ATTR_PERSISTENT       => FALSE,
				PDO::ATTR_TIMEOUT          => 6000, /* 1 h */
				PDO::CASE_LOWER            => TRUE,
				PDO::ATTR_STATEMENT_CLASS  => array("panel__db__pdo_statement")
			)
		);

	}	# Function

	/**
	 * @return maf__helpers__pdo_connection
	 */
	public function getConnection() {

		return $this->connection;

	}	# Function

	/**
	 * Execute SELECT query
	 * @param  string $_query_
	 * @param  int $_fetch_
	 * @return array
	 */
	public function query($_query_, $_fetch_ = PDO::FETCH_ASSOC) {

		$this->executed++;

		if( panel__settings__environment::isVerbose("panel.db.log_direct_queries") ) {

			panel__logger__query::getInstance()
			->logCommand("/* DIRECT QUERY CALL: */ \n {$_query_}");

		}	# if

		return $this->connection->query($_query_, $_fetch_);

	}	# Function

	/**
	 * Execute SELECT query
	 * @param  string $_query_
	 * @param  int $_fetch_
	 * @return array
	 */
	public function querySelect($_query_, $_fetch_ = PDO::FETCH_ASSOC) {

		$this->executed++;

		$names = "utf8.utf8_general_ci";

		if( $_query_ instanceof maf__sqlgen__abstract ) {

			if( $_query_->getNames() ) {
				$names = $_query_->getNames(); }

			$_query_ = $_query_->getSQL();

		}	# if

		$names = explode(".", $names);

		$this->connection->query( sprintf(
			"SET NAMES '%s' %s;",
			$names[0],
			(isset($names[1]) ? (" COLLATE '" . $names[1] . "'") : "")
		) );

		if( panel__settings__environment::isVerbose("panel.db.explain_queries") ) {

			$time    = microtime(true);
			$return  = $this->connection->query($_query_, $_fetch_);
			$trigger = panel__settings__environment::isVerbose("panel.db.explain_queries.trigger_time");
			$time    = round(microtime(true) - $time, $trigger);
			$rows_count = $return->rowCount();

			if( $time && $rows_count >= panel__settings__environment::isVerbose("panel.db.explain_queries.trigger_rows") ) {

				try {

					$explain = $this
					->connection
					->query("EXPLAIN " . $_query_, PDO::FETCH_ASSOC)
					->fetchAll()
					;

					panel__logger__query::getInstance()
					->logExplain($_query_, $explain, $time, $rows_count);

				} catch( maf__e__pdo_exception $e ) {}

			}	# if

		} else {

			$return = $this->connection->query($_query_, $_fetch_);

		}	# if

		if( !method_exists($return, "fetchAll") ) {

			$e = new panel__e__exception(
				"Query execution returned non fetchable object",
				E_USER_ERROR
			);

			$e["query"]  = $_query_;
			$e["return"] = $return;

			$e->setVerbose(TRUE);

			throw $e;

		}	# if

		return $return->fetchAll();

	}	# Function

	/**
	 * @throws panel__e__access_denied
	 * @param  string|array $_queries_
	 * @param  bool $_dropCache_ Drop cache?
	 * @return array Query Statement
	 */
	public function queryIUD($_queries_, $_dropCache_ = TRUE) {

		/**
		 * Test connection and reconnect if fail
		 */
		try {

			$this->testConnection();

		} catch( exception $e ) {

			$this->connect();
			$this->testConnection();

		}	# try

		if( !is_array($_queries_) ) {
			$_queries_ = array($_queries_); }

		$statement = array(
			"time"	         => date("Y-m-d H:i:s"),
			"host"           => $this->connection->getHost(),
			"last_insert_id" => 0,
			"affected_rows"  => 0,
			"queries"        => array()
		);

		$time = microtime(true);

		foreach( $_queries_ as $_sql_key => $_sql ) {

			if( $_sql instanceof maf__sqlgen__abstract ) {

				if( !is_null($_sql->getUse()) ) {

					$_use = "USE `" . $_sql->getUse() . "`;";
					$this->connection->query((string) $_use);

				}	# if

				if( !is_null($_sql->getNames()) ) {

					$_names = explode(".", $_sql->getNames());


				} else {

					$_names = array('latin1', 'latin1_general_ci');

				}	# if

				$this->connection->query( sprintf(
					"SET NAMES '%s' %s;",
					$_names[0],
					(isset($_names[1]) ? (" COLLATE '" . $_names[1] . "'") : "")
				) );

				$_sql = $_sql->getSQL();

			}	# if

			switch( TRUE ) {

			case ( is_string($_sql) ):

				$statement["queries"][$_sql_key] = array(
					"return"         => $this->connection->query((string) $_sql),
					"last_insert_id" => $this->connection->lastInsertID(),
					"sql"            => $_sql,
					"use"            => isset_or($_use),
					"names"          => isset_or($_names)
				);

				break;

			default:
				$e = new panel__e__exception("Unsupported query given.");
				$e[] = $_sql;
				throw $e;
				break;

			}	# switch

			$this->executed++;
			unset($_use, $_names);

			if( $statement["queries"][$_sql_key]["last_insert_id"] ) {
				$statement["last_insert_id"] = $statement["queries"][$_sql_key]["last_insert_id"]; }

			if( !($statement["queries"][$_sql_key]["return"] instanceof PDOStatement ) ) {
				throw new panel__e__exception("Given non PDOStatement object from mysql"); }

			$statement["affected_rows"] += $statement["queries"][$_sql_key]["return"]->rowCount();
			$statement["queries"][$_sql_key]["affected_rows"] = $statement["queries"][$_sql_key]["return"]->rowCount();

		}	# foreach

		$statement["mtime"] = microtime(true) - $time;

		if( panel__settings__environment::isLogQuerires() ) {
			panel__logger__query::getInstance()->logStatement($statement); }

		return $statement;

	}	# Function

	public function __call($_function_, array $_arguments_) {

		return call_user_func_array(
			array($this->connection, (string) $_function_),
			$_arguments_
		);

	}	# Function

	public function beginTransaction() {

		panel__logger__query::getInstance()
		->logCommand("START TRANSACTION;");

		$this->connection->beginTransaction();

	}	# Function

	public function commit() {

		panel__logger__query::getInstance()
		->logCommand("COMMIT;");

		$this->connection->commit();

	}	# Function

	public function rollback() {

		panel__logger__query::getInstance()
		->logCommand("ROLLBACK;");

		$this->connection->rollback();

	}	# Function

	/**
	 * @return panel__db__insert
	 */
	public function insert() {

		return panel__db__insert::create();

	}	# Function

	/**
	 * @return panel__db__select
	 */
	public function select() {

		return panel__db__select::create();

	}	# Function

	public function selectVariable($_) {

		return $this->rawSelect("SELECT @$_; ")->extractValue(0, "@$_");

	}	# Function

	public function getLastError() {

		return $this->selectVariable("error");

	}	# Function

	/**
	 * @return panel__db__raw_select
	 */
	public function rawSelect($_sql_ = NULL, $_iud_ = FALSE) {

		return panel__db__raw_select::create($_sql_, $_iud_);

	}	# Function

	/**
	 * @return panel__db__update
	 */
	public function update() {

		return panel__db__update::create();

	}	# Function

	/**
	 * @return panel__db__call
	 */
	public function call() {

		return panel__db__call::create();

	}	# Function

	/**
	 * @return panel__db__replace
	 */
	public function replace() {

		return panel__db__replace::create();

	}	# Function

	/**
	 * @return panel__db__delete
	 */
	public function delete() {

		return panel__db__delete::create();

	}	# Function

}	# Class

/* EOF */